module Orator

  # A base for the orators to split off of.  It handles callbacks, context
  # forwarding, and holding methods.
  #
  # @abstract
  class Base

    # Initialize the orator with the context.
    #
    # @param context [Object] the context to run in.
    # @param event [Hash] the event mapping.
    def initialize(context, event)
      @context = context
      @event   = event
    end

    # Whether or not the event should be prevented.
    #
    # @return [Boolean]
    def prevent_event?
      @prevent_event
    end

    # Forces the orator to prevent the event.
    #
    # @return [Boolean]
    def prevent_event!(new_value = true)
      @prevent_event = new_value
    end

    # The event name that this context is handling.  Note that it is not the
    # event that the client sent but the one this is handling (for example,
    # `socket.missing` is something this could be handling while `user.name`
    # could be what was sent).
    #
    # @return [String]
    def event
      @event[:event]
    end

    # Handles missing methods by delegating them to the context.  Should
    # never be called directly.
    #
    # @return [Object]
    def method_missing(method, *args, &block)
      super unless respond_to_missing?(method)

      @context.public_send(method, *args, &block)
    end

    # Lets Ruby know that there are some undefined methods on this class.
    #
    # @return [Boolean]
    def respond_to_missing?(method, include_private = false)
      @context.respond_to?(method, include_private)
    end

    # This forwards {#send} on to the context.
    #
    # @return [Object]
    def send(*args, &block)
      @context.send(*args, &block)
    end

    # Runs the callbacks.  IT uses #run_callback? to check if the callback can
    # be run.
    #
    # @param type [Symbol, String] the type of callback to run.
    # @param args [Array<Object>] the arguments that are passed to the
    #   callback.
    # @return [Array<Object>] the results.
    def run_callbacks(type, args)
      self.class.send("#{type}_list").map do |callback|
        next unless callback[1].nil? or run_callback?(callback[1])

        runner, = callback[0]

        if runner.is_a? Symbol
          method(runner).call(*args)
        else
          instance_exec(*args, &runner)
        end

      end
    end

    # Checks the options for the callback to see if it can be run in this
    # context.
    #
    # @param cb [Hash] the callback information.
    # @return [Boolean]
    def run_callback?(options)
      if !options[:only].empty?
        options[:only].map(&:to_s).include?(event)
      elsif !options[:except].empty?
        !options[:except].map(&:to_s).include?(event)
      else
        true
      end
    end

    private :run_callback?

    class << self

      # This manages the orator's name.  If an argument is passed, the name
      # is set to that.  Otherwise, it returns the orator's name.
      #
      # @param value [String, nil] the new name.
      # @return [String] the orator name.
      def orator_name(value = nil)
        if value
          @_orator_name = value
        end

        @_orator_name ||= self.name.gsub(/Orator\z/, "").underscore
      end

      # This handles the before callbacks that our orator needs.  It can accept
      # a method or a block, and given the two, chooses the method over the
      # block.
      #
      # @example
      #   before do; something; end
      # @example
      #   before :some_method
      # @param method [Symbol] the method that will be called for the before
      #   callback.
      # @yields []
      def before(method = nil, options = nil, &block)
        options = options || method || {}
        method  = block || method

        before_list << [method, _coerce_to_array(options)]
      end

      def _coerce_to_array(options)
        opts = options.dup
        opts[:only] = [opts[:only]].flatten.compact
        opts[:except] = [opts[:except]].flatten.compact
        opts
      end

      private :_coerce_to_array

      # This sets up the before list on the first call, and returns the list
      # on the first and subsequent calls.
      #
      # @return [Set<Symbol, Block>] the before callback list.
      def before_list
        @before_list ||= Set.new
      end

      # This handles the after callbacks that our operator needs.  It can accept
      # a method or a block, and given the two, chooses the method over the
      # block.
      #
      # @example
      #   after do; something; end
      # @example
      #   after :some_method
      # @param method [Symbol] the method that will be called for the after
      #   callback.
      # @yields []
      def after(method = nil, options = nil, &block)
        if block_given?
          after_list << [block, method]
        else
          after_list << [method, options] if method
        end
      end

      # This sets up the after list on the first call, and returns the list on
      # the first and subsequent calls.
      #
      # @return [Set<Symbol, Block>] the after callback list.
      def after_list
        @after_list ||= Set.new
      end

      # This is called when this class is inherited from.  We use this to make
      # sure that each subclass inherits the event list as well as the before
      # and after methods.
      #
      # @param klass [Base]
      def inherited(klass)
        klass.instance_variable_set(:@before_list, before_list.dup)
        klass.instance_variable_set(:@after_list,  after_list.dup )
      end
    end

  end

end
